﻿using System;
using Mjolnir.InfraEstrutura.Servicos;
using Mjolnir.Modelo.Ponteiros;

namespace Mjolnir.Modelo.Matrizes
{
  public class ServicoMatriz3x3 : ServicoBase<Matriz3x3, FabricaMatriz<Matriz3x3>>, IServicoMatriz<Matriz3x3>
  {
    /// <summary>
    /// Setar o valor.
    /// </summary>
    /// <param name="valor">valor.</param>
    public void setarValor(double[] valor)
    {
      entidade.M00 = valor[0];
      entidade.M01 = valor[1];
      entidade.M02 = valor[2];
      entidade.M10 = valor[3];
      entidade.M11 = valor[4];
      entidade.M12 = valor[5];
      entidade.M20 = valor[6];
      entidade.M21 = valor[7];
      entidade.M22 = valor[8];
    }

    /// <summary>
    /// Setar o valor.
    /// </summary>
    /// <param name="valor">valor.</param>
    public void setarValor(Matriz3x3 valor)
    {
      entidade.M00 = valor.M00;
      entidade.M01 = valor.M01;
      entidade.M02 = valor.M02;
      entidade.M10 = valor.M10;
      entidade.M11 = valor.M11;
      entidade.M12 = valor.M12;
      entidade.M20 = valor.M20;
      entidade.M21 = valor.M21;
      entidade.M22 = valor.M22;
    }

    /// <summary>
    /// Obter o valor.
    /// </summary>
    /// <param name="valor">valor.</param>
    public void obterValor(ref double[] valor)
    {
      valor[0] = entidade.M00;
      valor[1] = entidade.M01;
      valor[2] = entidade.M02;
      valor[3] = entidade.M10;
      valor[4] = entidade.M11;
      valor[5] = entidade.M12;
      valor[6] = entidade.M20;
      valor[7] = entidade.M21;
      valor[8] = entidade.M22;
    }

    /// <summary>
    /// Criar uma array.
    /// </summary>
    /// <param name="numero">numero.</param>
    /// <returns></returns>
    public Matriz3x3[] criarArray(int numero)
    {
      Matriz3x3[] retorno = fabrica.criar(numero);

      for (int i = 0; i < numero; i++)
      {
        retorno[i] = fabrica.criar();
      }

      return retorno;
    }

    public bool Inverso(Matriz3x3 fonte)
    {
      double a11 = fonte.M00;
      double a12 = fonte.M01;
      double a13 = fonte.M02;
      double a21 = fonte.M10;
      double a22 = fonte.M11;
      double a23 = fonte.M12;
      double a31 = fonte.M20;
      double a32 = fonte.M21;
      double a33 = fonte.M22;

      double b11 = a22 * a33 - a23 * a32;
      double b12 = a32 * a13 - a33 * a12;
      double b13 = a12 * a23 - a13 * a22;
      double b21 = a23 * a31 - a21 * a33;
      double b22 = a33 * a11 - a31 * a13;
      double b23 = a13 * a21 - a11 * a23;
      double b31 = a21 * a32 - a22 * a31;
      double b32 = a31 * a12 - a32 * a11;
      double b33 = a11 * a22 - a12 * a21;

      double determinante = a11 * b11 + a21 * b12 + a31 * b13;

      if (determinante == 0)
      {
        return false;
      }

      determinante = 1 / determinante;

      entidade.M00 = b11 * determinante;
      entidade.M00 = b12 * determinante;
      entidade.M00 = b13 * determinante;

      entidade.M00 = b21 * determinante;
      entidade.M00 = b22 * determinante;
      entidade.M00 = b23 * determinante;

      entidade.M00 = b31 * determinante;
      entidade.M00 = b32 * determinante;
      entidade.M00 = b33 * determinante;

      return true;
    }

    public void obterAnguloXYZ(ref Ponteiro3D saida)
    {
      double senA = entidade.M21;

      if (senA >= 1.0)
      {
        saida.x = Math.PI / 2;
        saida.y = 0;
        saida.z = Math.Atan2(-entidade.M10, entidade.M00);
      }
      else if (senA <= -1.0)
      {
        saida.x = -Math.PI / 2;
        saida.y = 0;
        saida.z = Math.Atan2(-entidade.M10, entidade.M00);
      }
      else
      {
        saida.x = Math.Asin(senA);
        saida.y = Math.Atan2(-entidade.M20, entidade.M22);
        saida.z = Math.Atan2(-entidade.M01, entidade.M11);
      }
    }

    public void setarAnguloXYZ(double x, double y, double z)
    {
      double senA = Math.Sin(x);
      double cosA = Math.Cos(x);
      double senB = Math.Sin(y);
      double cosB = Math.Cos(y);
      double senC = Math.Sin(z);
      double cosC = Math.Cos(z);

      entidade.M00 = cosC * cosB - senC * senA * senB;
      entidade.M01 = -senC * cosA;
      entidade.M02 = cosC * senB + senC * senA * cosB;
      
      entidade.M10 = cosC * cosB - senC * senA * senB;
      entidade.M11 = cosC * cosB - senC * senA * senB;
      entidade.M12 = cosC * cosB - senC * senA * senB;
      entidade.M20 = cosC * cosB - senC * senA * senB;
      entidade.M21 = cosC * cosB - senC * senA * senB;
      entidade.M22 = cosC * cosB - senC * senA * senB;
    }

    public void transformarVertice(Ponteiro3D posicao, ref Ponteiro3D saida)
    {
      this.transformarVertice(posicao.x, posicao.y, posicao.z, ref saida);
    }

    public void transformarVertice(double x, double y, double z, ref Ponteiro3D saida)
    {
      saida.x = entidade.M00 * x + entidade.M01 * y + entidade.M02 * z;
      saida.y = entidade.M10 * x + entidade.M11 * y + entidade.M12 * z;
      saida.z = entidade.M20 * x + entidade.M21 * y + entidade.M22 * z;
    }
  }
}
